package edu.gmu.dglidde2.ece612.scheduler;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

/**
 * A {@code Task} is a periodic task, job, or process with a hard real-time
 * deadline to be scheduled by a scheduler. A task has four essential
 * properties: phase, period, maximum execution time, and relative deadline.
 * Additionally, it may specify its processor affinity by defining a list of one
 * or more processors on which it is eligible to be scheduled.
 * 
 * @author Doug Glidden
 */
public class Task {

	private String name;
	private long phase;
	private long period;
	private long executionTime;
	private long relativeDeadline;
	private Set<String> processorNames;

	/**
	 * Determines if this {@code Task} is valid.
	 * 
	 * A task is valid under the following conditions:
	 * <ul>
	 * <li>The name has been set to a non-null value.</li>
	 * <li>The period has been set to a value greater than the phase. (The phase
	 * defaults to a valid value of 0 and is prevented from being set to an
	 * invalid value by input validation in the {@link #setPhase(long)} method.)
	 * </li>
	 * <li>The maximum execution time has been set to a value greater than 0.</li>
	 * <li>The relative deadline has been set to a value greater than or equal
	 * to the execution time.</li>
	 * </ul>
	 * 
	 * @return {@code true} if all of the above conditions are satisfied;
	 *         {@code false} otherwise.
	 */
	public boolean isValid() {
		if (name == null) {
			return false;
		}
		if (period <= phase) {
			return false;
		}
		if (executionTime <= 0) {
			return false;
		}
		if (relativeDeadline < executionTime) {
			return false;
		}

		return true;
	}

	/**
	 * Validates input strings and throws an {@link IllegalArgumentException} if
	 * an invalid input value is found.
	 * 
	 * @param inputValue
	 *            the value that was provided; {@code null} and empty strings
	 *            are rejected.
	 * @param inputName
	 *            the name of the input being validated.
	 */
	protected void validateInput(String inputValue, String inputName) {
		if (inputValue == null) {
			throw new IllegalArgumentException("Input " + inputName
					+ " must not be null.");
		}
		if (inputValue.isEmpty()) {
			throw new IllegalArgumentException("Input " + inputName
					+ " must not be an empty string.");
		}
	}

	/**
	 * Validates input long values for which 0 is a permitted value and throws
	 * an {@link IllegalArgumentException} if an invalid input value is found.
	 * 
	 * @param inputValue
	 *            the value that was provided; negative values are rejected.
	 * @param inputName
	 *            the name of the input being validated.
	 */
	protected void validateInput(long inputValue, String inputName) {
		if (inputValue < 0) {
			throw new IllegalArgumentException("Input " + inputName
					+ " must not be negative.");
		}
	}

	/**
	 * Validates input long values for which 0 is not a permitted value and
	 * throws an {@link IllegalArgumentException} if an invalid input value is
	 * found.
	 * 
	 * @param inputValue
	 *            the value that was provided; values that are less than or
	 *            equal to 0 are rejected.
	 * @param inputName
	 *            the name of the input being validated.
	 */
	protected void validateNonZeroInput(long inputValue, String inputName) {
		if (inputValue <= 0) {
			throw new IllegalArgumentException("Input " + inputName
					+ " must be positive.");
		}
	}

	/**
	 * Validates sets of strings and throws an {@link IllegalArgumentException}
	 * if an invalid input value is found.
	 * 
	 * @param inputValue
	 *            the value that was provided; {@code null} and empty sets as
	 *            well as sets that contain {@code null} or empty strings are
	 *            rejected.
	 * @param inputName
	 *            the name of the input being validated.
	 */
	protected void validateInput(Set<String> inputValue, String inputName) {
		if (inputValue == null) {
			throw new IllegalArgumentException("Input " + inputName
					+ " must not be null.");
		}
		if (inputValue.isEmpty()) {
			throw new IllegalArgumentException("Input " + inputName
					+ " must not be an empty set.");
		}
		if (inputValue.contains(null)) {
			throw new IllegalArgumentException("Input " + inputName
					+ " must not contain a null element.");
		}
		if (inputValue.contains("")) {
			throw new IllegalArgumentException("Input " + inputName
					+ " must not contain an empty string.");
		}
	}

	/**
	 * Returns this {@code Task}'s name.
	 * 
	 * @return the name.
	 */
	public String getName() {
		return name;
	}

	/**
	 * Sets this {@code Task}'s name.
	 * 
	 * @param name
	 *            the new name value. The name must be valid as defined by
	 *            {@link #validateInput(String, String)}.
	 */
	public void setName(String name) {
		validateInput(name, "name");
		this.name = name;
	}

	/**
	 * Returns this {@code Task}'s phase.
	 * 
	 * @return the phase.
	 */
	public long getPhase() {
		return phase;
	}

	/**
	 * Sets this {@code Task}'s phase.
	 * 
	 * @param phase
	 *            the new phase value. The phase must be valid as defined by
	 *            {@link #validateInput(long, String)}.
	 */
	public void setPhase(long phase) {
		validateInput(phase, "phase");
		this.phase = phase;
	}

	/**
	 * Returns this {@code Task}'s period.
	 * 
	 * @return the period.
	 */
	public long getPeriod() {
		return period;
	}

	/**
	 * Sets this {@code Task}'s period.
	 * 
	 * @param period
	 *            the new period value. The period must be valid as defined by
	 *            {@link #validateNonZeroInput(long, String)}.
	 */
	public void setPeriod(long period) {
		validateNonZeroInput(period, "period");
		this.period = period;
	}

	/**
	 * Returns this {@code Task}'s maximum execution time.
	 * 
	 * @return the maximum execution time.
	 */
	public long getExecutionTime() {
		return executionTime;
	}

	/**
	 * Sets this {@code Task}'s maximum execution time.
	 * 
	 * @param executionTime
	 *            the new maximum execution time value. The maximum execution
	 *            time must be valid as defined by
	 *            {@link #validateNonZeroInput(long, String)}.
	 */
	public void setExecutionTime(long executionTime) {
		validateNonZeroInput(executionTime, "execution time");
		this.executionTime = executionTime;
	}

	/**
	 * Returns this {@code Task}'s relative deadline.
	 * 
	 * @return the relative deadline.
	 */
	public long getRelativeDeadline() {
		return relativeDeadline;
	}

	/**
	 * Sets this {@code Task}'s relative deadline.
	 * 
	 * @param relativeDeadline
	 *            the new relative deadline value. The relative deadline must be
	 *            valid as defined by
	 *            {@link #validateNonZeroInput(long, String)}.
	 */
	public void setRelativeDeadline(long relativeDeadline) {
		validateNonZeroInput(relativeDeadline, "relative deadline");
		this.relativeDeadline = relativeDeadline;
	}

	/**
	 * Returns this {@code Task}'s processor names.
	 * 
	 * @return an unmodifiable copy of the processor names, or {@code null} if
	 *         the task has no processor names set.
	 */
	public Set<String> getProcessorNames() {
		if (processorNames == null) {
			return null;
		}
		return Collections.unmodifiableSet(processorNames);
	}

	/**
	 * Sets this {@code Task}'s processor names.
	 * 
	 * @param processorNames
	 *            the new processor names. The processor names must be valid as
	 *            defined by {@link #validateInput(Set, String)}.
	 */
	public void setProcessorNames(Set<String> processorNames) {
		validateInput(processorNames, "processor names");
		this.processorNames = new HashSet<>(processorNames);
	}

}
